package cn.wizzer.app.web.commons.ext.websocket.main.start;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;

import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**
 * webSocket
 * @author 22099
 */
@IocBean
public class TcpServer {

    private TcpServer(java.util.function.Consumer<TcpServer> function) {
        function.accept(this);
    }

    static TcpServer instance(int port){
        return new TcpServer(self->{
            self.port = port;
            self.tcpPort = new InetSocketAddress(port);
            NettyWebSocketChannelInitializer nettyWebSocketChannelInitializer = new NettyWebSocketChannelInitializer();
            self.nettyWebSocketChannelInitializer = nettyWebSocketChannelInitializer;
            self.setServerBootstrap(self.bootstrap());
        });
    }

    public TcpServer() {
    }

    @Inject("java:$conf.get('tcp.port')")
    private int port;

    @Inject("java:$conf.get('boss.thread.count')")
    private int bossCount;

    @Inject("java:$conf.get('worker.thread.count')")
    private int workerCount;

    @Inject("java:$conf.get('so.keepalive')")
    private boolean keepAlive;

    @Inject("java:$conf.get('so.backlog')")
    private int backlog;

    @Inject("refer:$ioc")
    private Ioc ioc;

    @Inject
    private PropertiesProxy conf;

    @Inject
    private NettyWebSocketChannelInitializer nettyWebSocketChannelInitializer;


    @IocBean(name = "tcpChannelOptions")
    private Map<ChannelOption<?>, Object> tcpChannelOptions() {
        Map<ChannelOption<?>, Object> options = new HashMap<ChannelOption<?>, Object>();
        options.put(ChannelOption.SO_KEEPALIVE, keepAlive);
        options.put(ChannelOption.SO_BACKLOG, backlog);
        return options;
    }

    @IocBean(name = "serverBootstrap")
    public ServerBootstrap bootstrap() {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup(), workerGroup())
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(nettyWebSocketChannelInitializer);
        Map<ChannelOption<?>, Object> tcpChannelOptions = tcpChannelOptions();
        Set<ChannelOption<?>> keySet = tcpChannelOptions.keySet();
        for (@SuppressWarnings("rawtypes") ChannelOption option : keySet) {
            b.option(option, tcpChannelOptions.get(option));
        }
        return b;
    }

    @IocBean(name = "bossGroup", depose = "shutdownGracefully")
    public NioEventLoopGroup bossGroup() {
        return new NioEventLoopGroup(bossCount);
    }

    @IocBean(name = "workerGroup", depose = "shutdownGracefully")
    public NioEventLoopGroup workerGroup() {
        return new NioEventLoopGroup(workerCount);
    }

    @IocBean(name = "tcpSocketAddress")
    public InetSocketAddress tcpSocketAddress(){
        return new InetSocketAddress(port);
    }

    @Inject
    private ServerBootstrap serverBootstrap;

    @Inject("tcpSocketAddress")
    private InetSocketAddress tcpPort;

    private Channel serverChannel;

    void start() throws Exception {
        System.out.println("##########################################");
        System.out.println("    websocket started with port:" + port);
        System.out.println("##########################################");
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        serverChannel =  serverBootstrap.bind(tcpPort).sync().channel().closeFuture().sync().channel();
    }

    @PreDestroy
    public void stop() throws Exception {

        serverChannel.close();
        serverChannel.parent().close();
    }

    public ServerBootstrap getServerBootstrap() {
        return serverBootstrap;
    }

    public void setServerBootstrap(ServerBootstrap serverBootstrap) {
        this.serverBootstrap = serverBootstrap;
    }

    public InetSocketAddress getTcpPort() {
        return tcpPort;
    }

    public void setTcpPort(InetSocketAddress tcpPort) {
        this.tcpPort = tcpPort;
    }
}